diff options
| author | joonhoekim <26rote@gmail.com> | 2025-06-20 11:47:15 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-06-20 11:47:15 +0000 |
| commit | abd9f950bbd95b9ad713a26d3fd8a7e0282b7c51 (patch) | |
| tree | aafc71d5ff23962c2d6d5e902c66ee070b7ac068 /app/api/auth/[...nextauth]/saml/provider.ts | |
| parent | 994defd6446ce20c4b4e0d6cc91688b0e64230a4 (diff) | |
(김준회) SAML 2.0 SSO (Knox Portal) 추가
Diffstat (limited to 'app/api/auth/[...nextauth]/saml/provider.ts')
| -rw-r--r-- | app/api/auth/[...nextauth]/saml/provider.ts | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/app/api/auth/[...nextauth]/saml/provider.ts b/app/api/auth/[...nextauth]/saml/provider.ts new file mode 100644 index 00000000..92099be0 --- /dev/null +++ b/app/api/auth/[...nextauth]/saml/provider.ts @@ -0,0 +1,128 @@ +import CredentialsProvider from "next-auth/providers/credentials" +import { getOrCreateSAMLUser, validateSAMLUserData } from '@/lib/users/saml-service' + +interface SAMLProviderOptions { + id: string + name: string + idp: { + sso_login_url: string + sso_logout_url: string + certificates: string[] + } + sp: { + entity_id: string + private_key: string + certificate: string + assert_endpoint: string + } +} + +export function SAMLProvider(options: SAMLProviderOptions) { + return CredentialsProvider({ + id: options.id, + name: options.name, + credentials: { + user: { + label: "User Data", + type: "text" + } + }, + async authorize(credentials) { + try { + if (!credentials?.user) { + console.error('No user data provided') + return null + } + + console.log('🔐 SAML Provider: Processing user data') + + // 사용자 데이터 파싱 (UTF-8 처리 개선) + const userDataString = credentials.user + console.log('🔤 Raw user data string:', userDataString.substring(0, 200) + '...') + + const userData = JSON.parse(userDataString) + + // 파싱된 데이터의 UTF-8 확인 + console.log('🔤 Parsed user data UTF-8 check:', { + name: userData.name, + nameLength: userData.name?.length, + charCodes: userData.name ? [...userData.name].map(c => c.charCodeAt(0)) : [] + }) + + if (!userData.id || !userData.email) { + console.error('Invalid SAML user data:', userData) + return null + } + + console.log('✅ SAML Provider: User authenticated successfully', { + id: userData.id, + email: userData.email, + name: userData.name + }) + + // 🔥 SAML 사용자 데이터 검증 + const isValidData = await validateSAMLUserData(userData) + if (!isValidData) { + console.error('Invalid SAML user data structure:', userData) + return null + } + + // 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회 + const dbUser = await getOrCreateSAMLUser({ + email: userData.email, + name: userData.name, + // companyId: userData.companyId, + // techCompanyId: userData.techCompanyId, + // ! domain = evcp 이면 vendor가 갖는 companyId, techCompanyId는 null + companyId: undefined, + techCompanyId: undefined, + domain: userData.domain + }) + + if (!dbUser) { + console.error('Failed to get or create SAML user') + return null + } + + // DB에서 가져온 실제 사용자 정보 반환 + const userResult = { + id: String(dbUser.id), // DB의 실제 ID + name: dbUser.name, // DB의 실제 이름 + email: dbUser.email, // DB의 실제 이메일 + companyId: dbUser.companyId, // DB의 실제 회사 ID + techCompanyId: dbUser.techCompanyId, // DB의 실제 기술회사 ID + domain: dbUser.domain, // DB의 실제 도메인 + imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL + } + + console.log('✅ SAML Provider: Returning user data to NextAuth:', userResult) + return userResult + } catch (error) { + console.error('❌ SAML Provider: Authentication failed', error) + return null + } + } + }) +} + +// SAML 로그인 URL 생성 헬퍼 함수 +export function getSAMLLoginUrl(options: SAMLProviderOptions): string { + const params = new URLSearchParams({ + SAMLRequest: 'placeholder', // 실제로는 createAuthnRequest()로 생성 + RelayState: options.sp.assert_endpoint, + }) + + return `${options.idp.sso_login_url}?${params.toString()}` +} + +// SAML 설정 검증 +export function validateSAMLOptions(options: SAMLProviderOptions): boolean { + const required = [ + options.idp.sso_login_url, + options.sp.entity_id, + options.sp.assert_endpoint + ] + + return required.every(field => field && field.length > 0) +} +
\ No newline at end of file |
